import hmac
import json
import logging
import hashlib
import secrets
import base64
from datetime import datetime

from lzstring import LZString
from apscheduler.schedulers.background import BackgroundScheduler
from Crypto.Cipher import ARC4

uvicorn_logger = logging.getLogger('uvicorn')

scheduler = BackgroundScheduler()

SALT_KEY = 'FM-HEADER-SALT'
SALT_FACTORS = [2025, 2001, 2002, 2025]

# 动态生成，无需预设
SALT_BYTES = b''
SALT = ''


def start_scheduler():
    def _generate_salt():
        timestamp = datetime.now().timestamp()
        generate_salt(timestamp)
        uvicorn_logger.info('Scheduled task triggered: daily generate salt.')

    if not SALT:
        _generate_salt()

    if not scheduler.running:
        try:
            [hour, minute] = [0, 0]

            scheduler.add_job(
                _generate_salt,
                'cron',
                hour=hour,
                minute=minute,
                misfire_grace_time=3600,
                id='generate_salt_job'
            )
            scheduler.start()
            uvicorn_logger.info(
                f"Scheduled task initialized: daily generate salt at {str(hour).zfill(2)}:{str(minute).zfill(2)} AM",
                extra={"operation": "scheduler"}
            )
        except Exception as e:
            uvicorn_logger.error(
                f"Scheduler initialization failed: {str(e)}",
                exc_info=True,
                extra={"operation": "scheduler"}
            )


def generate_salt(timestamp: int):
    global SALT_BYTES, SALT

    # 获取当前时间的年月日
    dt = datetime.utcfromtimestamp(timestamp)
    ymd = dt.strftime("%Y%m%d")

    # 拼接字符串并生成MD5哈希
    input_str = f"{SALT_KEY}-{ymd}"
    salt = hashlib.md5(input_str.encode())
    SALT_BYTES = salt.digest()
    SALT = salt.hexdigest()
    return SALT


def generate_trace(timestamp: int):
    if not SALT_BYTES or not SALT:
        generate_salt(timestamp)

    # 转换为可变的字节数组
    processed = bytearray(SALT_BYTES)

    # 应用每轮的可逆位操作
    for factor in SALT_FACTORS:
        add_val = (factor >> 8) & 0xFF  # 提取高8位作为加值
        xor_val = factor & 0xFF  # 提取低8位作为异或值

        for i in range(len(processed)):
            # 加法（模256）后异或
            processed[i] = (processed[i] + add_val) % 256
            processed[i] ^= xor_val

    # 转换为32位十六进制字符串
    result = processed.hex()
    return result


def reverse_trace(processed_salt: str, factors):
    processed = bytearray.fromhex(processed_salt)

    # 逆向处理每一轮
    for factor in reversed(factors):
        add_val = (factor >> 8) & 0xFF
        xor_val = factor & 0xFF

        for i in range(len(processed)):
            # 先异或再减法（模256）
            processed[i] ^= xor_val
            processed[i] = (processed[i] - add_val) % 256

    return bytes(processed).hex()


def generate_daily_key():
    if not SALT:
        return ''

    timestamp = datetime.now().timestamp()
    dt = datetime.utcfromtimestamp(timestamp)
    ymd = dt.strftime("%Y%m%d")
    return hashlib.sha256(f'{ymd}{SALT}'.encode()).hexdigest()[0:16]


def sort_and_stringify(data: dict) -> str:
    sorted_data = {k: data[k] for k in sorted(data.keys())}
    return json.dumps(sorted_data, ensure_ascii=False, separators=(',', ':'))


def hmac_sha256(sign_content: str, daily_key: str, dynamic_salt: str) -> str:
    secret_key = (daily_key + dynamic_salt).encode('utf-8')

    message = sign_content.encode('utf-8')

    hmac_obj = hmac.new(
        key=secret_key,
        msg=message,
        digestmod=hashlib.sha256
    )

    return hmac_obj.hexdigest()


def generate_signature(method: str, pathname: str, params: dict, timestamp: str, nonce: str, dfp: str):
    dynamic_salt = hashlib.sha256(timestamp[-6:].encode()).hexdigest()

    sign_content = '|'.join([
        method.upper(),
        pathname,
        sort_and_stringify(params),
        timestamp,
        nonce,
        dfp,
        dynamic_salt
    ])

    daily_key = generate_daily_key()
    server_sign = hmac_sha256(sign_content, daily_key, dynamic_salt)
    return server_sign


def compare_signature(client_sign: str, server_sign: str):
    return secrets.compare_digest(client_sign, server_sign)


def generate_chash(data: dict, key: str):
    return hmac_sha256(sort_and_stringify(data), key, '')


def compare_chash(client_chash: str, server_chash: str):
    return secrets.compare_digest(client_chash, server_chash)


def encryptRC4(plaintext: str, key: str) -> str:
    textBytes = bytes(plaintext, "latin1")
    keyBytes = bytes(key, "latin1")
    cipher = ARC4.new(keyBytes)
    ciphertext = cipher.encrypt(textBytes)
    return ciphertext.hex()


def decryptRC4(ciphertext: str, key: str) -> str:
    hextext = base64.b64decode(ciphertext).hex()
    cipherBytes = bytes('', "latin1").fromhex(hextext)
    keyBytes = bytes(key, "latin1")
    cipher = ARC4.new(keyBytes)
    textBytes = cipher.decrypt(cipherBytes)
    return textBytes.decode("latin1")


class DfpModel:
    MAX_RISK_SCORE = 20
    VALID_CANVAS_NAME = 'PING CARD AUTH'

    def __init__(self, valid: bool, hash_: str = '', content=None):
        if content is None:
            content = []
        self.hash = hash_
        self.content = content
        self.valid = valid

    def check_risk(self):
        try:
            return self._check_risk()
        except Exception:
            return True

    def _check_risk(self):
        if not self.valid:
            return True

        risk_score = 0
        content = self.content

        # 参数索引映射表（根据content顺序）
        params_array = [
            "userAgent",
            "webdriver",
            "language",
            "colorDepth",
            "deviceMemory",
            "hardwareConcurrency",
            "screenResolution",
            "availableScreenResolution",
            "timezoneOffset",
            "timezone",
            "sessionStorage",
            "localStorage",
            "indexedDb",
            "addBehavior",
            "openDatabase",
            "cpuClass",
            "platform",
            "plugins",
            "canvas",
            "webgl",
            "webglVendorAndRenderer",
            "hasLiedLanguages",
            "hasLiedResolution",
            "hasLiedOs",
            "hasLiedBrowser",
            "touchSupport",
            "fonts",
            "audio"
        ]
        params_map = {params_array[i]: i for i in range(len(params_array))}

        # 1. 指纹数量检测
        if len(self.content) < len(params_map):
            return True

        # 2. 插件检测规则
        plugin_count = sum(content[params_map["plugins"]])
        risk_score += max((5 - plugin_count) * 3, 0)  # 每少1个 PDF 插件 +3分

        # 3. 关键风险检测 (检测条件, 风险分)
        risk_items = [
            # ----------------- 浏览器特征 -----------------
            (not content[params_map['sessionStorage']], 12),
            (not content[params_map['localStorage']], 12),
            (not content[params_map['indexedDb']], 12),

            # ----------------- 自动化特征 -----------------
            (content[params_map["webdriver"]], 15),  # WebDriver存在
            ("HeadlessChrome" in content[params_map["userAgent"]], 20),  # 无头浏览器特征

            # ----------------- 硬件异常 -----------------
            (content[params_map["hardwareConcurrency"]] > 16, 4),  # 异常高CPU核心数
            (content[params_map["deviceMemory"]] not in [4, 8, 16, 'not available'], 4),  # 非常用内存配置

            # ----------------- 屏幕特征 -----------------
            (
                abs(content[params_map["screenResolution"]][1] - content[params_map["availableScreenResolution"]][
                    1]) > 100,
                5),
            # 屏幕高度差异过大
            (content[params_map["screenResolution"]][0] > 4096, 3),  # 超宽屏幕

            # ----------------- 环境伪造 -----------------
            (content[params_map["hasLiedLanguages"]], 6),  # 语言伪造
            (content[params_map["hasLiedResolution"]], 6),  # 分辨率伪造
            (content[params_map["hasLiedOs"]], 6),  # 系统伪造
            (content[params_map["hasLiedBrowser"]], 6),  # 浏览器伪造

            # ----------------- 高级特征 -----------------
            ("VMware" in content[params_map["webglVendorAndRenderer"]], 9),  # 虚拟机特征
            (content[params_map["fonts"]] < 20, 5),  # 字体数量过少
            (content[params_map["touchSupport"]][0] > 0 and "Win32" in content[params_map['platform']], 5),  # PC端出现触摸支持
        ]

        # 4. 执行所有静态检测项
        for condition, score in risk_items:
            if condition:
                risk_score += score

        # 5. 时区矛盾检测
        if self._check_timezone_mismatch(content[params_map["timezoneOffset"]]):
            risk_score += 8

        # 6. Canvas指纹异常
        if self._check_canvas_abnormal(content[params_map["canvas"]]):
            risk_score += 10

        print(f'risk_score: {risk_score}', self.hash, self.content)

        # 风险等级判定
        if risk_score >= self.__class__.MAX_RISK_SCORE:
            return True
        else:
            return False

    def _check_timezone_mismatch(self, offset):
        """时区与IP地理位置的矛盾检测（伪代码）"""
        # 需接入IP地理接口获取真实时区
        # real_offset = get_ip_timezone_offset()
        # return abs(offset - real_offset) > 60
        return False  # 暂不实现

    def _check_canvas_abnormal(self, canvas):
        """Canvas指纹异常检测"""
        is_supported = canvas.get('supported', False)
        if not is_supported:
            return True

        name = canvas.get('name', '')
        if name != self.__class__.VALID_CANVAS_NAME:
            return True

        is_private_mode = canvas.get('pmd', True)
        if is_private_mode:
            return True

        return False


def parse_dfp(dfp: str):
    dfp_array = dfp.split('.', 1)
    if len(dfp_array) != 2:
        return DfpModel(False)

    hash_ = dfp_array[0]
    key = hash_ + 'fingerprint'
    dfp = dfp_array[1]
    try:
        rawB64 = decryptRC4(dfp, key)
        data = LZString.decompressFromBase64(rawB64)
        return DfpModel(True, hash_, json.loads(data))
    except Exception:
        return DfpModel(False, hash_)


if __name__ == '__main__':
    ts = int(datetime.now().timestamp())
    encoded = generate_trace(ts)
    print('ts', ts)
    print('encoded', encoded)
    print('salt', reverse_trace(encoded, SALT_FACTORS))
